/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module div(
	input	wire				clk,
	input	wire				rst_n,

	input	wire				div_req_i,
    input   wire[3:0]           mux_mul_ctrl_i,
	input	wire[`RegDataBus]	div_operand_1_i,
	input	wire[`RegDataBus]	div_operand_2_i,

	output	reg[`RegDataBus]	div_quotient_o,
	output	reg[`RegDataBus]	div_remainder_o,
	output	reg				    v_flag_o,
	output	reg				    div_busy_o
	);

	localparam DIV_IDLE = 3'b001;
	localparam DIV_BUSY = 3'b010;
	localparam DIV_DONE = 3'b100;

    wire i_inst_div = (mux_mul_ctrl_i == 4'b0101);
    wire i_inst_divu = (mux_mul_ctrl_i == 4'b0110);
    wire i_inst_rem = (mux_mul_ctrl_i == 4'b0111);
    wire i_inst_remu = (mux_mul_ctrl_i == 4'b1000);

	reg[`RegDataBus]	operand_1, operand_2;

    wire op_1_neg = (i_inst_div | i_inst_rem) & operand_1[63];
    wire op_2_neg = (i_inst_div | i_inst_rem) & operand_2[63];
    wire[`RegDataBus] op_1_complemented = ~operand_1 + `XLEN'h1;
    wire[`RegDataBus] op_2_complemented = ~operand_2 + `XLEN'h1;
    wire[`RegDataBus] div_op_1 =
        ({`XLEN{i_inst_divu | i_inst_remu | !op_1_neg}} & operand_1)
        | ({`XLEN{op_1_neg}} & op_1_complemented);
    wire[`RegDataBus] div_op_2 =
        ({`XLEN{i_inst_divu | i_inst_remu | !op_2_neg}} & operand_2)
        | ({`XLEN{op_2_neg}} & op_2_complemented);

	reg[`DXLEN:0]		dividend;
	reg[`DXLEN:0]		dividend_trans;
	reg[`DXLEN:0]		dividend_temp;
	reg[`XLEN:0]		dividend_high;	
	reg[`RegDataBus]	divisor;
    reg[`RegDataBus]	res_rem;
	reg[6:0]			loop;
	reg[2:0]			cur_state;
	reg[2:0]			next_state;
	reg					signed_overflow;

	wire result_neg = (i_inst_div | i_inst_rem) & (operand_1[63] ^ operand_2[63]);

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE) begin
			cur_state <= DIV_IDLE;
            dividend_trans <= 129'h0;
		end else begin
			cur_state <= next_state;
			if (div_busy_o) begin
				loop <= loop - 1;
				dividend_trans <= dividend;
            end else begin
				loop <= 7'h40;
			end
		end
	end

	always @(*) begin
		if (rst_n == `RESET_ENABLE) begin
            div_busy_o = `DISABLE;
			dividend = 129'h0;
			divisor = `XLEN'h0;
			next_state = DIV_IDLE;
		end else begin
			case (cur_state)
				DIV_IDLE: begin
					if (div_req_i) begin
						if (div_operand_2_i == `ZERO) begin
							div_quotient_o = {`XLEN{1'b1}};
							div_remainder_o = div_operand_1_i;
							// should set exception in ex mudule
							v_flag_o = 1'b0;
							div_busy_o = `DISABLE;
						end else if ((i_inst_div || i_inst_rem)
								&& div_operand_1_i == `XLEN'h8000000000000001
								&& div_operand_2_i == {`XLEN{1'b1}}) begin
							div_quotient_o = div_operand_1_i;
							div_remainder_o = `ZERO;
							v_flag_o = 1'b1;
							div_busy_o = `DISABLE;
						end else begin
							operand_1 = div_operand_1_i;
							operand_2 = div_operand_2_i;
							dividend = {`XLEN'h0, div_op_1, 1'b0};
							divisor = div_op_2;
							div_busy_o = `ENABLE;
							next_state = DIV_BUSY;
						end
					end
				end

				DIV_BUSY: begin
					if (loop != 7'h0) begin
						dividend_high = dividend_trans[`DXLEN:`XLEN] - {1'b0, divisor};
						if (dividend_high[`XLEN]) begin
							dividend_temp = dividend_trans << 1;
							dividend = {dividend_temp[`DXLEN:1], 1'b0};
						end else begin
							dividend_temp = {dividend_high, dividend_trans[`XLEN-1:0]} << 1;
							dividend = {dividend_temp[`DXLEN:1], 1'b1};
						end
						next_state = DIV_BUSY;
					end else begin
						dividend_high = dividend_trans[`DXLEN:`XLEN] - {1'b0, divisor};
						if (dividend_high[`XLEN]) begin
							dividend_temp = dividend_trans << 1;
							dividend = {dividend_temp[`DXLEN:1], 1'b0};
						end else begin
							dividend_temp = {dividend_high, dividend_trans[`XLEN-1:0]} << 1;
							dividend = {dividend_temp[`DXLEN:1], 1'b1};
						end

						res_rem = dividend[`DXLEN:`XLEN] >> 1;
						div_quotient_o = result_neg ? (~dividend[`DATA_MSB:0] + `XLEN'h1)
							: dividend[`DATA_MSB:0];
						div_remainder_o = ((i_inst_div | i_inst_rem) && operand_1[`XLEN-1])
							? (~res_rem + `XLEN'h1) : res_rem;
						v_flag_o = 1'b0;
						div_busy_o = `DISABLE;
						next_state = DIV_DONE;
					end
				end

				DIV_DONE: begin
					next_state = DIV_IDLE;
				end

				default: begin
                    next_state = DIV_IDLE;
				end
			endcase
		end
	end

endmodule
